/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * RSOrtographic.cpp
 *
 *  Created on: 2010/03/08
 *      Author: Alexandre Pires ( c.alexandre.pires@gmail.com )
 */

#include "RSOrthographic.h"
#include "RSValueElement.h"
#include "RSFloat.h"

namespace Realsphere {

    RSOrthographic::RSOrthographic ( const QString& rScopeId ) :
        RSProjectionTechniqueElement( RS_ORTOGRAPHIC, rScopeId )
    {
        mHorizontalMagnification = NULL;
        mVerticalMagnification = NULL;
    }

    RSOrthographic::~RSOrthographic ()
    {
        mHorizontalMagnification = NULL;
        mVerticalMagnification = NULL;
    }

    RSFloat* RSOrthographic::HorizontalMagnification () const
    {
        return mHorizontalMagnification;
    }

    RSFloat* RSOrthographic::VerticalMagnification () const
    {
        return mVerticalMagnification;
    }

    RSError RSOrthographic::SetHorizontalMagnification ( RSFloat* rValue )
    {
        rValue->SetTag ( RS_HORIZONTAL_MAGNIFICATION_TAG );
        RSError error = AddValueElement ( rValue );
        return error;
    }

    RSError RSOrthographic::SetVerticalMagnification ( RSFloat* rValue )
    {
        rValue->SetTag ( RS_VERTICAL_MAGNIFICATION_TAG );
        RSError error = AddValueElement ( rValue );
        return error;
    }

    RSError RSOrthographic::SetHorizontalMagnification ( const RSFloat& rValue )
    {
        if ( mHorizontalMagnification == NULL )
            return RS_VALUE_UNDEFINED;

        mHorizontalMagnification->SetValue ( rValue );

        return RS_NO_ERROR;
    }

    RSError RSOrthographic::SetVerticalMagnification ( const RSFloat& rValue )
    {
        if ( mVerticalMagnification == NULL )
            return RS_VALUE_UNDEFINED;

        mVerticalMagnification->SetValue ( rValue );

        return RS_NO_ERROR;
    }

    RSError RSOrthographic::SetHorizontalMagnification ( const float& rValue )
    {
        RSFloat value ( rValue );
        RSError error = SetHorizontalMagnification ( value );
        return error;
    }

    RSError RSOrthographic::SetVerticalMagnification ( const float& rValue )
    {
        RSFloat value ( rValue );
        RSError error = SetVerticalMagnification ( value );
        return error;
    }

    RSError RSOrthographic::BeforeAddValueElementValidation ( RSValueElement* rValueElement )
    {
        RSError
                error =
                        RSProjectionTechniqueElement::BeforeAddValueElementValidation ( rValueElement );

        if ( error == RS_INVALID_VALUE )
        {
            if ( rValueElement->Tag () == RS_HORIZONTAL_MAGNIFICATION_TAG
                    || rValueElement->Tag () == RS_VERTICAL_MAGNIFICATION_TAG )
            {
                error = RS_NO_ERROR;
            }
        }

        return error;
    }

    RSError RSOrthographic::BeforeRemoveValueElementValidation ( RSValueElement* rValueElement )
    {
        RSError
                error =
                        RSProjectionTechniqueElement::BeforeRemoveValueElementValidation ( rValueElement );

        if ( error == RS_INVALID_VALUE )
        {
            if ( rValueElement->Tag () == RS_HORIZONTAL_MAGNIFICATION_TAG
                    || rValueElement->Tag () == RS_VERTICAL_MAGNIFICATION_TAG )
            {
                error = RS_NO_ERROR;
            }
        }

        return error;
    }

    bool RSOrthographic::AfterAddValueElementValidation ( RSValueElement* rValueElement )
    {
        bool
                handled =
                        RSProjectionTechniqueElement::AfterAddValueElementValidation ( rValueElement );

        if ( !handled )
        {

            RSFloat* value = RSFloatCast ( rValueElement );

            if ( rValueElement->Tag () == RS_HORIZONTAL_MAGNIFICATION_TAG )
            {
                if ( mHorizontalMagnification != NULL )
                    RemoveChildElement ( mHorizontalMagnification );

                mHorizontalMagnification = value;
                handled = true;

                emit HorizontalMagnificationChanged ( this, mHorizontalMagnification );
            }
            else if ( rValueElement->Tag () == RS_VERTICAL_MAGNIFICATION_TAG )
            {
                if ( mVerticalMagnification != NULL )
                    RemoveChildElement ( mVerticalMagnification );

                mVerticalMagnification = value;
                handled = true;

                emit VerticalMagnificationChanged ( this, mVerticalMagnification );
            }
        }

        return handled;
    }

    bool RSOrthographic::AfterRemoveValueElementValidation ( RSValueElement* rValueElement )
    {
        bool
                handled =
                        RSProjectionTechniqueElement::AfterRemoveValueElementValidation ( rValueElement );

        if ( !handled )
        {
            if ( rValueElement->Tag () == RS_HORIZONTAL_MAGNIFICATION_TAG )
            {
                mHorizontalMagnification = NULL;
                handled = true;
                emit HorizontalMagnificationChanged ( this, mHorizontalMagnification );
            }
            else if ( rValueElement->Tag () == RS_VERTICAL_MAGNIFICATION_TAG )
            {
                mVerticalMagnification = NULL;
                handled = true;
                emit VerticalMagnificationChanged ( this, mVerticalMagnification );
            }
        }

        return handled;
    }

    QMatrix4x4 RSOrthographic::ProjectionMatrix () const
    {
        float left = -1;
        float right = 1;
        float top = 1;
        float bottom = -1;

        float z_near = RS_DEFAULT_Z_NEAR;
        float z_far = RS_DEFAULT_Z_FAR;

        RSFloat* znear_value_element = ZNear ();
        RSFloat* zfar_value_element = ZFar ();

        if ( znear_value_element != NULL && zfar_value_element != NULL )
        {
            z_near = znear_value_element->Value ();
            z_far = zfar_value_element->Value ();
        }

        RSFloat* aspect_ratio_value_element = AspectRatio ();

        if ( aspect_ratio_value_element != NULL )
        {

            float aspect_ratio = aspect_ratio_value_element->Value ();

            if ( mHorizontalMagnification != NULL )
            {
                right = mHorizontalMagnification->Value ();
                top = ( right * aspect_ratio );
            }
            else if ( mVerticalMagnification != NULL )
            {
                top = mVerticalMagnification->Value ();
                right = ( top * aspect_ratio );
            }

        }
        else if ( mHorizontalMagnification != NULL && mVerticalMagnification
                != NULL )
        {

            right = mHorizontalMagnification->Value ();
            top = mVerticalMagnification->Value ();
        }

        left = -right;
        bottom = -top;

        QMatrix4x4 projection;
        projection.ortho ( left, right, bottom, top, z_near, z_far );
        return projection;
    }
}
